package cn.itsource.ymcc.service.impl;

import cn.itsource.ymcc.constant.KillConstants;
import cn.itsource.ymcc.constant.RConst;
import cn.itsource.ymcc.domain.KillActivity;
import cn.itsource.ymcc.domain.KillCourse;
import cn.itsource.ymcc.dto.PreOrderDto;
import cn.itsource.ymcc.mapper.KillActivityMapper;
import cn.itsource.ymcc.mapper.KillCourseMapper;
import cn.itsource.ymcc.service.IKillCourseService;
import cn.itsource.ymcc.util.AssertUtil;
import cn.itsource.ymcc.util.CodeGenerateUtils;
import cn.itsource.ymcc.util.StrUtils;
import cn.itsource.ymcc.vo.KillVo;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zongchangjiang
 * @since 2023-04-15
 */
@Service
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {
    @Autowired
    private KillActivityMapper killActivityMapper;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Override
    public boolean save(KillCourse entity) {
        KillActivity killActivity = killActivityMapper.selectById(entity.getActivityId());
        entity.setStartTime(killActivity.getBeginTime());
        entity.setEndTime(killActivity.getEndTime());
        entity.setTimeStr(killActivity.getTimeStr());
        return super.save(entity);
    }

    @Override
    public KillCourse getById(Serializable id) {
        return (KillCourse)redisTemplate.opsForHash().get(KillConstants.KEY_KILL_COURSE, id);
    }

    @Override
    public List killsList() {
        BoundHashOperations<String, Object, Object> boundHashOps = redisTemplate.boundHashOps(KillConstants.KEY_KILL_COURSE);
        return boundHashOps.values();
    }

    /**
     * 什么叫做对应的活动   我要去将 (特殊 就用当前时间)
     * 将对应活动的数据往redis里面去推
     */
    @Override
    public void publish() {
        Date date = new Date();//当前时间
        //查询出来的就是 我要放入到 redis中的
        List<KillCourse> killCourses = baseMapper.selectList(new QueryWrapper<KillCourse>()
                .eq("publish_status", RConst.Num.INT_0)
                .lt("start_time", date)
                .gt("end_time", date));
        //将数据往redis存
        Map<String,Object> killCoursMap = new HashMap<>();
        for (KillCourse killCours : killCourses) {
            //课程库存预热
            redisTemplate.opsForValue().set(KillConstants.KILL_STORE+":"+killCours.getId(),killCours.getKillCount());

            killCours.setKillCode(StrUtils.getRandomString(10));
            //存储1个 方式一  存储所有的课程
//            redisTemplate.opsForHash().put(KillConstants.KEY_KILL_COURSE,killCours.getId(),killCours);
            //方式二
            killCoursMap.put(killCours.getId().toString(),killCours);
            //修改状态
            killCours.setPublishStatus(RConst.Num.INT_1); //1秒杀中
            killCours.setPublishTime(date);
            baseMapper.updateById(killCours);
        }

        //redis的批量添加
        BoundHashOperations<String, Object, Object> boundHashOps =
                redisTemplate.boundHashOps(KillConstants.KEY_KILL_COURSE);
        boundHashOps.putAll(killCoursMap);

    }

    /**
     * 问题 我需要些什么参数  userId(用户ID) KillCourseId(那个课程id)  KillCount购买数量  killCode验证码
     *     我要返回什么参数  返回 秒杀订单号
     *     我这个里面要做什么事情
     *        参数校验
     *           1.购买的数量
     * @return
     */
    @Override
    public String kill(KillVo killVo){
        //可以去实现  过滤 筛选 方案 防止高并发 不写
        //验证秒杀验证
        AssertUtil.isNotNull(killVo.getKillCode(),"验证不能为空");
        //课程id验证
        AssertUtil.isNotNull(killVo.getKillCourseId(),"课程ID不能为空");
        //用户是否已经秒杀过当前课程  疑问?
        AssertUtil.isFalse(redisTemplate.hasKey(KillConstants.KEY_PREFIX_PRE_ORDER+":"+killVo.getUserId()),"请勿重复秒杀同一课程！");

        Object o = redisTemplate.opsForHash().get(KillConstants.KEY_KILL_COURSE, killVo.getKillCourseId().toString());
        AssertUtil.isNotNull(o,"参数异常");
        KillCourse killCourse = (KillCourse) o;
        AssertUtil.isTrue(killCourse.getKillCode().equalsIgnoreCase(killVo.getKillCode()),"验证输入错误");
        //秒杀时间是否合法
        Date now = new Date();
        Date startTime = killCourse.getStartTime();
        Date endTime = killCourse.getEndTime();
        AssertUtil.isTrue(startTime.before(now)&&endTime.after(now),"秒杀活动时间不合法!");
        //秒杀数量限制
        Integer killCount = killVo.getKillCount();
        AssertUtil.isTrue(killCourse.getKillLimit() >= killCount && killCount>0,
                "每人限购"+killCourse.getKillLimit()+"份！");
        //2. Redisson分布式锁：信号量减库存  库存预热的key
        RSemaphore semaphore = redissonClient.getSemaphore(KillConstants.KILL_STORE + ":" + killVo.getKillCourseId());
        boolean flag = semaphore.tryAcquire(killVo.getKillCount());
        String orderSn = "";
        if(flag){ //为true 表示抢到了
            orderSn = CodeGenerateUtils.generateOrderSn(killVo.getUserId());
            //3. 创建订单号，准备“预创订单数据”  用户抢到了
            //伪生成订单
            PreOrderDto preOrderDto = new PreOrderDto();
            preOrderDto.setOrderNo(orderSn);
            preOrderDto.setUserId(killVo.getUserId());
            preOrderDto.setTitle("秒杀订单");
            preOrderDto.setOrderType(RConst.Num.INT_1);
            preOrderDto.setPrice(killCourse.getKillPrice());
            preOrderDto.setCount(killVo.getKillCount());
            preOrderDto.setCourseId(killCourse.getCourseId());
            preOrderDto.setCourseName(killCourse.getCourseName());
            preOrderDto.setCoursePic(killCourse.getCoursePic());
            preOrderDto.setKillCourseId(killCourse.getId());
            //存放到redis里面去
            redisTemplate.opsForValue().set(KillConstants.KEY_PREFIX_PRE_ORDER+":"+orderSn,preOrderDto);
        }
        //4.发送延迟消息  判断用户在5分钟之内有没有 生成支付单
        rocketMQTemplate.syncSend(
                KillConstants.TOPIC_ORDER_TIMEOUT+":"+KillConstants.TAGS_ORDER_TIMEOUT,
                MessageBuilder.withPayload(orderSn).build(),
                3000, 9
        );
        //6. 保存用户秒杀记录到Redis防止重复秒杀  KILL_RECORD_用户id_秒杀课程id
        redisTemplate.opsForValue().set(KillConstants.KEY_PREFIX_PRE_ORDER+":"+killVo.getUserId(),"1");
        //返回秒杀订单号
        return orderSn;
    }
}
